﻿
Imports System.IO
Imports System.Threading
Imports std

Public Class processor
    Public Const backupFileSuffix As String = ".logmerger.backup"
    Public Const backupConfignaming As String = "processor"
    Protected name As String = Nothing
    Protected handlers As vector(Of loghandler) = Nothing
    Protected config As pointer(Of configParser) = Nothing
    Protected files As vector(Of String) = Nothing
    Protected processingFiles As [set](Of String) = Nothing
    Protected filesmutex As Mutex = Nothing
    Protected WithEvents filewatcher As FileSystemWatcher = Nothing
    Protected thread As Thread = Nothing
    Protected remove As Boolean = False
    Protected sleeptime As Int64 = 0
    Protected includeSubfolders As Boolean = False

    Public Function configParameter(Of T)(ByRef key As String, Optional ByRef defaultValue As T = Nothing) As T
        assert(Not config Is Nothing AndAlso Not (+config) Is Nothing AndAlso Not isemptystring(name) _
               , "should initial process before configParameter")

        Dim rtn As T = Nothing
        If (+config).getparameter(name, key, rtn, defaultValue) Then
            Return rtn
        End If

        If (+config).getparameter(backupConfignaming, key, rtn, defaultValue) Then
            Return rtn
        End If

        Return defaultValue
    End Function

    Public Sub initial(ByRef name As String, ByRef config As configParser)
        copy(Me.name, name)
        Me.config = New pointer(Of configParser)(config)
    End Sub

    Public Sub filethread()
        Dim reader As StreamReader = Nothing
        Try
            Dim filesleeptime As Int64 = 0
            filesleeptime = configParameter(configNaming.processor.filesleeptime _
                                            , configNaming.defaultValue.processor.filesleeptime)
            Dim readsleeptime As Int64 = 0
            readsleeptime = configParameter(configNaming.processor.readsleeptime _
                                             , configNaming.defaultValue.processor.readsleeptime)
            While True
                assert(filesmutex.WaitOne(), "cannot lock filesmutex when catch a file to process.")
                assert(Not files Is Nothing, "files is nothing when getnextfile.")
                Dim i As Int64
                For i = 0 To files.size() - 1
                    If processingFiles.find(files(i)) = processingFiles.end() Then
                        Exit For
                    End If
                Next

                If i < files.size() Then
                    Dim filename As String = files(i)
                    processingFiles.insert(filename)
                    filesmutex.ReleaseMutex()
                    If File.Exists(filename) Then
                        Try
                            reader = New StreamReader(New FileStream(filename, FileMode.Open _
                                                                     , FileAccess.Read _
                                                                     , FileShare.ReadWrite Or FileShare.Delete))
                        Catch ex As Exception
                            raiseError("cannot create reader for file " + filename + ", " + ex.Message _
                                       , errorHandle.errorType.warning)
                        End Try
                        If Not reader Is Nothing Then
                            Dim firstalert As Boolean = True
                            Do
                                Dim line As String = Nothing
                                Do
                                    line = readerHelper.readline(reader)
                                    If isemptystring(line) Then
                                        Exit Do
                                    Else
                                        For i = 0 To handlers.size() - 1
                                            handlers(i).oneline(line)
                                        Next
                                    End If
                                    sleep(readsleeptime)
                                Loop

                                If remove Then
                                    Try
                                        File.Delete(filename)
                                    Catch ex As Exception
                                        If firstalert Then
                                            raiseError("cannot delete thisfile " + filename _
                                                       + ", assume the file is locking by other program to write log" _
                                                       + ", continue monitoring, " + ex.Message _
                                                       , errorHandle.errorType.system)
                                            firstalert = False
                                        End If
                                    End Try
                                Else
                                    Try
                                        File.Move(filename, filename + backupFileSuffix)
                                    Catch ex As Exception
                                        If firstalert Then
                                            raiseError("cannot rename thisfile " + filename _
                                                       + ", assume the file is locking by other program to write log" _
                                                       + ", continue monitoring, " + ex.Message _
                                                       , errorHandle.errorType.system)
                                            firstalert = False
                                        End If
                                    End Try
                                End If

                                If isemptystring(line) Then
                                    sleep(filesleeptime)
                                End If
                            Loop While File.Exists(filename)

                            closestream(reader)
                        End If
                    End If
                Else
                    filesmutex.ReleaseMutex()
                End If
                sleep(filesleeptime)
            End While
        Catch
        Finally
            closestream(reader)
        End Try
    End Sub

    Public Sub service()
        Dim threads As vector(Of Thread) = Nothing
        Dim threadCount As Int64 
        Try
            Dim logpath As String = Nothing
            logpath = configParameter(configNaming.processor.logpath, configNaming.defaultValue.processor.logpath)
            Dim filefilter As String = Nothing
            filefilter = configParameter(configNaming.processor.filefilter _
                                         , configNaming.defaultValue.processor.filefilter)
            sleeptime = configParameter(configNaming.processor.monitorsleeptime _
                                        , configNaming.defaultValue.processor.monitorsleeptime)
            remove = configParameter(configNaming.processor.remove, configNaming.defaultValue.processor.remove)
            includeSubfolders = configParameter(configNaming.processor.includeSubfolders _
                                                , configNaming.defaultValue.processor.includeSubfolders)

            If Not filewatcher Is Nothing Then
                filewatcher.Dispose()
                filewatcher = Nothing
            End If
            filewatcher = New saferFileSystemWatcher()
            filewatcher.Path = logpath
            filewatcher.Filter = filefilter
            filewatcher.NotifyFilter = NotifyFilters.FileName
            filewatcher.IncludeSubdirectories = includeSubfolders
            filewatcher.EnableRaisingEvents = True
            filewatcher_Changed(Nothing, Nothing)

            threads = New vector(Of Thread)
            threadCount = configParameter(configNaming.processor.filethreadcount _
                                          , configNaming.defaultValue.processor.filethreadcount)
            Dim i As Int64
            For i = 0 To threadCount - 1
                threads.push_back(convertor.cast(Of Thread)(Nothing))
            Next
            While True
                For i = 0 To threadCount - 1
                    If threads(i) Is Nothing OrElse Not threads(i).IsAlive() Then
                        If Not threads(i) Is Nothing Then
                            raiseError("caught thread exited", errorHandle.errorType.warning)
                        End If
                        threads(i) = New Thread(AddressOf filethread)
                        threads(i).Start()
                    End If
                Next
                sleep(sleeptime)
            End While
        Catch ex As Exception
            raiseError("caught exception, " + ex.Message + ", shutdown service", errorHandle.errorType.warning)
        Finally
            If Not threads Is Nothing Then
                Dim i As Int64
                For i = 0 To threadCount - 1
                    If Not threads(i) Is Nothing AndAlso threads(i).IsAlive Then
                        threads(i).Abort()
                        threads(i).Join()
                    End If
                Next
            End If
        End Try
    End Sub

    Public Sub start()
        If handlers Is Nothing Then
            handlers = New vector(Of loghandler)
        Else
            handlers.clear()
        End If
        Dim str As String
        str = configParameter(configNaming.processor.counter, configNaming.defaultValue.processor.counter)
        If Not isemptystring(str) Then
            handlers.push_back(New counter())
            handlers(handlers.size() - 1).initial(name + character.underline + str, +config)
        End If

        str = configParameter(configNaming.processor.merger, configNaming.defaultValue.processor.merger)
        If Not isemptystring(str) Then
            handlers.push_back(New merger())
            handlers(handlers.size() - 1).initial(name + character.underline + str, +config)
        End If

        str = configParameter(configNaming.processor.remotelogger, configNaming.defaultValue.processor.remotelogger)
        If Not isemptystring(str) Then
            handlers.push_back(New remotelogger())
            handlers(handlers.size() - 1).initial(name + character.underline + str, +config)
        End If

        thread = New Thread(AddressOf service)
        thread.Start()
    End Sub

    Public Sub [stop]()
        If Not thread Is Nothing Then
            thread.Abort()
            thread.Join()
            thread = Nothing
        End If
    End Sub

    Public Sub New()
        files = New vector(Of String)
        processingFiles = New [set](Of String)
        filesmutex = New Mutex()
    End Sub

    Private Sub filewatcher_Changed(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) _
                                                                                            Handles filewatcher.Changed
        assert(filesmutex.WaitOne(), "cannot lock filesmutex")
        files.clear()
        Try
            If includeSubfolders Then
                files = Directory.GetFiles(filewatcher.Path, filewatcher.Filter, SearchOption.AllDirectories)
            Else
                files = Directory.GetFiles(filewatcher.Path, filewatcher.Filter, SearchOption.TopDirectoryOnly)
            End If
        Catch ex As Exception
            raiseError("cannot get files from path " + filewatcher.Path + "with filter " + filewatcher.Filter _
                       + ", " + ex.Message, errorHandle.errorType.system)
        End Try
        filesmutex.ReleaseMutex()
    End Sub

    Private Sub filewatcher_Created(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) _
        Handles filewatcher.Created
        filewatcher_Changed(sender, e)
    End Sub

    Private Sub filewatcher_Renamed(ByVal sender As Object, ByVal e As System.IO.RenamedEventArgs) _
        Handles filewatcher.Renamed
        filewatcher_Changed(sender, e)
    End Sub
End Class
